/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.swing.action;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EventListener;
import java.util.LinkedList;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;

import org.apache.log4j.Logger;
import org.isqlviewer.util.IsqlToolkit;

/**
 * Event delegate system to process events outside of the standard swing thread.
 * <p>
 * 
 * @author Mark A. Kobold
 * @version 1.0
 */
public final class SwingEventManager implements ActionListener, MouseListener, FocusListener, ChangeListener,
        ItemListener {

    private EventListenerList eventDelegates = new EventListenerList();
    private Thread eventThread = null;
    private final LinkedList<EventTask> taskHeap = new LinkedList<EventTask>();
    private final ThreadGroup eventGroup;
    private final EventLoop runLoop;
    private final Logger logger = IsqlToolkit.getApplicationLogger();
    private final ActionMap actionMap = new ActionMap();

    public SwingEventManager() {

        this("isql-swing-loop");
    }

    public SwingEventManager(String eventGroupName) {

        this.eventGroup = new ThreadGroup(eventGroupName);
        this.runLoop = new EventLoop();
    }

    public synchronized void stop() {

        if (eventThread != null)
            eventThread.interrupt();
        purgeTasks();
        eventThread = null;
    }

    public synchronized void start() {

        if (eventThread == null) {
            eventThread = new Thread(eventGroup, runLoop, "isql-swing-events");
            eventThread.start();
        } else {
            notify();
        }
    }

    public void registerAction(String name, Action action) {

        actionMap.put(name, action);
    }

    public Action getAction(String key) {

        return actionMap.get(key);
    }

    /**
     * @param renderer
     */
    public void enqueueRunnable(Runnable runnable) {

        if (runnable != null) {
            enqueueEventTask(new RunnableEventTask(runnable));
        }
    }

    /**
     * Adds an actionListener delegate to recieve ActionEvents from this manager.
     * <p>
     * 
     * @param listener actionLister to add for processing actionEvents.
     */
    public void addActionListener(ActionListener listener) {

        eventDelegates.add(ActionListener.class, listener);
    }

    /**
     * Removes an actionListener delegate to stop recieving ActionEvents from this manager.
     * <p>
     * 
     * @param listener actionLister to remove from processing ActionEvents.
     */
    public void removeActionListener(ActionListener listener) {

        eventDelegates.remove(ActionListener.class, listener);
    }

    /**
     * Adds an changeListener delegate to recieve ChangeEvents from this manager.
     * <p>
     * 
     * @param listener changeLister to add for processing ChangeEvents.
     */
    public void addChangeListener(ChangeListener listener) {

        eventDelegates.add(ChangeListener.class, listener);
    }

    /**
     * Removes a changeListener delegate to stop recieving ChangeEvents from this manager.
     * <p>
     * 
     * @param listener changeLister to remove from processing ChangeEvents.
     */
    public void removeChangeListener(ChangeListener listener) {

        eventDelegates.remove(ChangeListener.class, listener);
    }

    /**
     * Adds an itemListener delegate to recieve ItemEvents from this manager.
     * <p>
     * 
     * @param listener itemLister to add for processing ItemEvents.
     */
    public void addItemListener(ItemListener listener) {

        eventDelegates.add(ItemListener.class, listener);
    }

    /**
     * Removes an itemListener delegate to stop recieving ItemEvents from this manager.
     * <p>
     * 
     * @param listener itemLister to remove from processing ItemEvents.
     */
    public void removeItemListener(ItemListener listener) {

        eventDelegates.remove(ItemListener.class, listener);
    }

    /**
     * Adds a focusListener delegate to recieve FocusEvents from this manager.
     * <p>
     * 
     * @param listener focusLister to add for processing FocusEvents.
     */
    public void addFocusListener(FocusListener listener) {

        eventDelegates.add(FocusListener.class, listener);
    }

    /**
     * Removes a focusListener delegate to stop recieving FocusEvents from this manager.
     * <p>
     * 
     * @param listener focusLister to remove from processing FocusEvents.
     */
    public void removeFocusListener(FocusListener listener) {

        eventDelegates.remove(FocusListener.class, listener);
    }

    public void focusGained(FocusEvent event) {

        EventTask eventTask = new EventTask(FocusListener.class, "focusGained", event);
        enqueueEventTask(eventTask);
    }

    public void focusLost(FocusEvent event) {

        EventTask eventTask = new EventTask(FocusListener.class, "focusLost", event);
        enqueueEventTask(eventTask);
    }

    public void actionPerformed(ActionEvent event) {

        EventTask eventTask = new EventTask(ActionListener.class, "actionPerformed", event);
        enqueueEventTask(eventTask);
    }

    public void itemStateChanged(ItemEvent event) {

        EventTask eventTask = new EventTask(ItemListener.class, "itemStateChanged", event);
        enqueueEventTask(eventTask);

    }

    public void stateChanged(ChangeEvent event) {

        EventTask eventTask = new EventTask(ChangeListener.class, "stateChanged", event);
        enqueueEventTask(eventTask);
    }

    public void mouseClicked(MouseEvent e) {

    }

    public void mouseEntered(MouseEvent e) {

    }

    public void mouseExited(MouseEvent e) {

    }

    public void mousePressed(MouseEvent e) {

    }

    public void mouseReleased(MouseEvent e) {

    }

    protected synchronized void enqueueEventTask(EventTask task) {

        synchronized (taskHeap) {
            taskHeap.addLast(task);
        }
        start();
    }

    protected void purgeTasks() {

        synchronized (taskHeap) {
            while (taskHeap.size() > 0) {
                try {
                    taskHeap.removeFirst();
                } catch (Exception e) {
                }
            }
        }
    }

    protected synchronized EventTask nextTask() {

        try {
            while (!Thread.interrupted()) {

                EventTask task = null;
                if (!taskHeap.isEmpty()) {
                    task = taskHeap.removeFirst();
                }

                if (task == null) {
                    wait();
                } else {
                    return task;
                }
            }
        } catch (InterruptedException ex) {
        } // fall through
        return null; // on interrupt
    }

    /** set thread_ to null to indicate termination * */
    protected synchronized void clearThread() {

        eventThread = null;
    }

    protected class EventLoop implements Runnable {

        public void run() {

            try {
                for (;;) {
                    EventTask task = nextTask();
                    if (task == null) {
                        break;
                    }
                    if (task instanceof Runnable) {
                        Runnable runnableTask = (Runnable) task;
                        try {
                            runnableTask.run();
                        } catch (Throwable t) {
                            logger.error("Error dispatching event", t);
                        }
                    } else {
                        // normal event.
                        delegateEvent(task);
                    }
                }
            } finally {
                System.runFinalization();
                clearThread();
            }
        }

        private void delegateEvent(EventTask task) {

            Method method = task.getMethod();
            if (method == null) {
                return;
            }

            EventListener[] delegates = eventDelegates.getListeners(task.getEventClass());
            Object[] eventParameters = new Object[]{task.getEventObject()};
            for (int i = 0; i < delegates.length; i++) {
                EventListener listener = delegates[i];
                try {
                    method.invoke(listener, eventParameters);
                } catch (InvocationTargetException err) {
                    logger.error("error dispatching event", err.getTargetException());
                } catch (Throwable t) {
                    logger.error("Error dispatching event", t);
                }
            }
        }
    }

}
